#include "SpellCheckEngineInterface.h"
#include "SpellCheckUserInterface.h"

wxSpellCheckEngineInterface::wxSpellCheckEngineInterface()
{
    m_AlwaysReplaceMap.clear();
    m_AlwaysIgnoreList.IsEmpty();
    m_pSpellUserInterface = nullptr;
    m_bPersonalDictionaryModified = false;
    m_bEngineInitialized = false;
}

wxSpellCheckEngineInterface::~wxSpellCheckEngineInterface()
{
    if (m_pSpellUserInterface != nullptr)
    {
        delete m_pSpellUserInterface;
        m_pSpellUserInterface = nullptr;
    }
}

void wxSpellCheckEngineInterface::SetSpellCheckUserInterface(wxSpellCheckUserInterface* pDlg)
{
    // delete the old user interface
    if (m_pSpellUserInterface != nullptr)
        delete m_pSpellUserInterface;

    // set the new user interface and tell it that this is it's spell check engine
    m_pSpellUserInterface = pDlg;
    if (m_pSpellUserInterface != nullptr)
        pDlg->SetSpellCheckEngine(this);
}

int wxSpellCheckEngineInterface::GetUserCorrection(const wxString& strMisspelling)  //Show function will show the dialog and not return until the user makes a decision
{
    // Populate the listbox with suggestions
    // At this point, we have to wait for user input.  This is where having the dialog and the spell checking as different classes would be handy
    int nLastAction = m_pSpellUserInterface->PresentSpellCheckUserInterface(strMisspelling);

    if (nLastAction == wxSpellCheckUserInterface::ACTION_REPLACE_ALWAYS)
    {
        m_AlwaysReplaceMap[m_pSpellUserInterface->GetMisspelledWord()] = m_pSpellUserInterface->GetReplacementText();
    }
    else if (nLastAction == wxSpellCheckUserInterface::ACTION_IGNORE_ALWAYS)
    {
        m_AlwaysIgnoreList.Add(m_pSpellUserInterface->GetMisspelledWord());
    }
    else if (nLastAction == wxSpellCheckUserInterface::ACTION_CLOSE)
    {
        return wxSpellCheckUserInterface::ACTION_CLOSE;
    }

    return ((nLastAction == wxSpellCheckUserInterface::ACTION_REPLACE) ||
            (nLastAction == wxSpellCheckUserInterface::ACTION_REPLACE_ALWAYS)) ? wxSpellCheckUserInterface::ACTION_REPLACE : wxSpellCheckUserInterface::ACTION_IGNORE;
}


void wxSpellCheckEngineInterface::DefineContext(const wxString& strText, long nOffset, long nLength)
{
    // This is kind of a kludge, but to determine the context of this word,
    //  grab the 50 characters before the nOffset and the 50 characters + nLength after nOffset
    // Then, do a Find from the front and the back of this 100 + nLength character string looking
    //  for a space character.  Trim off this space character and all characters preceding/after
    //  it based on if it's before or after the misspelled word.
    // Also, if there are NOT 50 characters before the nOffset don't bother trimming off at the space.
    // Likewise for less than 50 characters following the misspelled word.
    // Special allowances should be made to not have newline characters (\r or \n) in the context.

    if (strText.Length() < 50)
    {
        m_Context.SetContext(strText);
        m_Context.SetOffset(nOffset);
        m_Context.SetLength(nLength);
    }
    else
    {
        wxString strLocalText = strText;
        strLocalText.Replace(_T("\r"), _T(" "));
        strLocalText.Replace(_T("\n"), _T(" "));

        long nStartPosition = 0;
        bool bTrimFront = false;
        long nOffsetTrimmed = nOffset;
        if (nOffset > 50)
        {
            nStartPosition = nOffset - 50;
            nOffsetTrimmed = 50;
            bTrimFront = TRUE;
        }

        bool bTrimEnd = false;
        long nEndPosition = wxString::npos;
        if ((unsigned)(nStartPosition + nLength + 50) < strLocalText.Length())
        {
            nEndPosition = (nLength + 50);
            bTrimEnd = TRUE;
        }

        nEndPosition += nOffset - nStartPosition;  // Without this, we're only grabbing the number of characters for starting at the misspelled word
        wxString strContext;
        if ((unsigned)nEndPosition == wxString::npos)
            strContext = strLocalText.Mid(nStartPosition);
        else
            strContext = strLocalText.Mid(nStartPosition, nEndPosition);
        // Remove characters before the first space character
        if (bTrimFront)
        {
            if (strContext.Contains(_T(" ")))
            {
                nOffsetTrimmed -= strContext.Find(' ') + 1; // Figure out how much of the offset was trimmed off by remove the characters before the first space
                strContext = strContext.AfterFirst(' ');
            }
        }

        // Remove characters before the first space character
        if (bTrimEnd)
        {
            if (strContext.Contains(_T(" ")))
                strContext = strContext.BeforeLast(' ');
        }

        m_Context.SetContext(strContext);
        m_Context.SetOffset(nOffsetTrimmed);
        m_Context.SetLength(nLength);
    }
}

bool wxSpellCheckEngineInterface::AddOptionToMap(SpellCheckEngineOption& option)
{
    // Return a boolean indicating whether or now the spell check engine options actually changed
    bool bOptionsChanged = false;
    wxString strOptionName = option.GetName();

    if (!strOptionName.IsEmpty())
    {
        OptionsMap::iterator it = m_Options.find(strOptionName);
        // If either the option isn't in the map yet or the value is different, then update the options
        if ((it == m_Options.end()) || (it->second.GetValueAsString() != option.GetValueAsString()))
        {
            m_Options[strOptionName] = option;
            bOptionsChanged = true;
        }
    }
    return bOptionsChanged;
}

void wxSpellCheckEngineInterface::ApplyOptions()
{
    for (OptionsMap::iterator it = m_Options.begin(); it != m_Options.end(); ++it)
    {
        SetOption(it->second);
    }
}

void wxSpellCheckEngineInterface::ShowOption(const wxString& strOption, bool bShow /*= true*/)
{
    OptionsMap::iterator it = m_Options.find(strOption);
    if (it != m_Options.end())
        it->second.SetShowOption(bShow);
}

const wxCharBuffer wxSpellCheckEngineInterface::ConvertToUnicode(const wxString& inputString)
{
#if wxUSE_UNICODE
    wxString strEncoding = GetCharacterEncoding();
    if (strEncoding != wxEmptyString)
    {
        wxCSConv conv(strEncoding);
        wxCharBuffer returnBuffer(conv.cWC2MB(inputString.wc_str(*wxConvCurrent)));
        return returnBuffer;
    }
    else
    {
        wxCharBuffer returnBuffer(wxConvUTF8.cWC2MB(inputString.wc_str(*wxConvCurrent)));
        return returnBuffer;
    }
#else
    wxCharBuffer returnBuffer = inputString.c_str();
    return returnBuffer;
#endif
}

wxString wxSpellCheckEngineInterface::ConvertFromUnicode(const char* inputBuffer)
{
#if wxUSE_UNICODE
    wxString strEncoding = GetCharacterEncoding();
    if (strEncoding != wxEmptyString)
    {
        wxCSConv conv(strEncoding);
        wxString returnString(conv.cMB2WC(inputBuffer), *wxConvCurrent);
        return returnString;
    }
    else
    {
        wxString returnString(wxConvUTF8.cMB2WC(inputBuffer), *wxConvCurrent);
        return returnString;
    }
#else
    wxString returnString(inputBuffer);
    return returnString;
#endif
}

